package com.zhanhong.opencvdemo.presenter;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Point;
import android.support.annotation.NonNull;

import com.zhanhong.opencvdemo.R;
import com.zhanhong.opencvdemo.config.AnswerSheetConfig;
import com.zhanhong.opencvdemo.config.CropConfig;
import com.zhanhong.opencvdemo.model.CircleModel;
import com.zhanhong.opencvdemo.utils.BitmapUtils;
import com.zhanhong.opencvdemo.utils.LogUtils;
import com.zhanhong.opencvdemo.utils.ThreadUtils;
import com.zhanhong.opencvdemo.utils.TimeUtils;
import com.zhanhong.opencvdemo.value.FilePath;
import com.zhanhong.opencvdemo.view.CropActivity;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Range;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import me.pqpo.smartcropperlib.view.CropImageView;

public class CropPresenterImpl implements CropPresenter {

    private final CropActivity mActivity;
    private final ArrayList<String> mLogList = new ArrayList<>();

    public CropPresenterImpl(CropActivity cropActivity) {
        mActivity = cropActivity;
    }

    @Override
    public void crop(final CropImageView imageView) {
        ThreadUtils.runOnSubThread(() -> {
            try {
                Bitmap bitmap = imageView.crop();
                if (bitmap == null) {
                    throw new RuntimeException("图片裁剪失败");
                } else {
                    final String cropPath = createCropFilePath();
                    FileOutputStream fos = new FileOutputStream(new File(cropPath));
                    bitmap.compress(CompressFormat.PNG, 100, fos);
                    fos.flush();
                    fos.close();
                    ThreadUtils.runOnUIThread(() -> mActivity.onCropComplete(cropPath));
                }
            } catch (Exception e) {
                mActivity.onCropError(e);
                e.printStackTrace();
            }
        });
    }

    @Override
    public void analyzeSrcBitmap(final Bitmap srcBitmap) {
        ThreadUtils.runOnSubThread(() -> {

            final Point[] points = new Point[4];

            final int targetLength = (int) (srcBitmap.getWidth() * AnswerSheetConfig.CIRCLE_RADIUS_RATE / AnswerSheetConfig.TARGET_CIRCLE_RATE);

            Bitmap targetBitmap = BitmapUtils.loadBitmap(R.mipmap.answer_sheet_target, targetLength, targetLength);

            mLogList.clear();

            mLogList.add("src width : " + srcBitmap.getWidth());

            mLogList.add("src height : " + srcBitmap.getHeight());

            mLogList.add("target width : " + targetBitmap.getWidth());

            mLogList.add("target height : " + targetBitmap.getHeight());


            //自动分析定位点

            long startTime = new Date().getTime();
            mLogList.add("auto analyze start at : " + TimeUtils.getTimeStr(startTime));

            final int total = 7;

            //(1)=====================================================//

            mLogList.add("step 1 start");

            stepDealProgress(1, total, "预处理答题图像", true);

            Mat srcMat = new Mat();
            Utils.bitmapToMat(srcBitmap, srcMat);

            //1源图灰度化
            Mat grayMat = doGray(srcMat, 1, total);

            //2源图模糊降噪
            Mat blurMat = doBlur(grayMat, CropConfig.CROP_SOURCE_CONFIG.blurSize, 1, total);

            //3源图二值化
            Mat binaryMat = doBinary(blurMat, CropConfig.CROP_SOURCE_CONFIG.binaryBlockSize, CropConfig.CROP_SOURCE_CONFIG.binaryC, 1, total);

            final Bitmap binaryBitmap = createBitmapAsSrc(srcBitmap);
            Utils.matToBitmap(binaryMat, binaryBitmap);
            BitmapUtils.save(binaryBitmap, new File(FilePath.CACHE_DIR, "CropBinaryBitmap.png"));
            Mat targetMat = new Mat();
            Utils.bitmapToMat(targetBitmap, targetMat);

            mLogList.add("step 1 completed");

            //(1)=====================================================//


            //(2)=====================================================//

            mLogList.add("step 2 start");

            stepDealProgress(2, total, "预处理定位标记", true);

            //1目标灰度化
            Mat targetGrayMat = doGray(targetMat, 1, total);

            //2目标模糊降噪
            Mat targetBlurMat = doBlur(targetGrayMat, CropConfig.CROP_TARGET_CONFIG.blurSize, 1, total);

            //3目标二值化
            Mat targetBinaryMat = doBinary(targetBlurMat, CropConfig.CROP_TARGET_CONFIG.binaryBlockSize, CropConfig.CROP_TARGET_CONFIG.binaryC, 1, total);

            final Bitmap targetBinaryBitmap = createBitmapAsSrc(targetBitmap);
            Utils.matToBitmap(targetBinaryMat, targetBinaryBitmap);
            BitmapUtils.save(targetBinaryBitmap, new File(FilePath.CACHE_DIR, "TargetBinaryBitmap.png"));
            mLogList.add("step 2 completed");

            //(2)=====================================================//


            //(3)=====================================================//

            mLogList.add("step 3 start");

            int matchMethod = Imgproc.TM_SQDIFF;

            mLogList.add("match method : " + "Imgproc.TM_SQDIFF");

            int resultHeight = binaryMat.rows() / 2 - targetBinaryMat.rows() + 1;
            int resultWidth = binaryMat.cols() / 2 - targetBinaryMat.cols() + 1;

            String stepName1 = "左上定位标记分析";
            stepDealProgress(3, total, stepName1, true);
            Mat srcMat1 = new Mat(binaryMat, new Range(0, binaryMat.rows() / 2), new Range(0, binaryMat.cols() / 2));
            Mat resultMat1 = new Mat(resultHeight, resultWidth, binaryMat.type());
            Imgproc.matchTemplate(srcMat1, targetBinaryMat, resultMat1, matchMethod);
            Core.MinMaxLocResult minMaxLocResult1 = Core.minMaxLoc(resultMat1);
            Point matchLocation1 = getMatchLocation(matchMethod, minMaxLocResult1);
            points[0] = new Point(matchLocation1.x + targetBinaryMat.cols() / 2, matchLocation1.y + targetBinaryMat.rows() / 2);

            mLogList.add("step 3 point 1 : x = " + points[0].x + ", y = " + points[0].y);

            String stepName2 = "右上定位标记分析";
            stepDealProgress(4, total, stepName2, true);
            Mat srcMat2 = new Mat(binaryMat, new Range(0, binaryMat.rows() / 2), new Range(binaryMat.cols() / 2, binaryMat.cols()));
            Mat resultMat2 = new Mat(resultHeight, resultWidth, binaryMat.type());
            Imgproc.matchTemplate(srcMat2, targetBinaryMat, resultMat2, matchMethod);
            Core.MinMaxLocResult minMaxLocResult2 = Core.minMaxLoc(resultMat2);
            Point matchLocation2 = getMatchLocation(matchMethod, minMaxLocResult2);
            points[1] = new Point(matchLocation2.x + targetBinaryMat.cols() / 2 + binaryMat.cols() / 2, matchLocation2.y + targetBinaryMat.rows() / 2);

            mLogList.add("step 3 point 2 : x = " + points[1].x + ", y = " + points[1].y);

            String stepName3 = "右下定位标记分析";
            stepDealProgress(5, total, stepName3, true);
            Mat srcMat3 = new Mat(binaryMat, new Range(binaryMat.rows() / 2, binaryMat.rows()), new Range(binaryMat.cols() / 2, binaryMat.cols()));
            Mat resultMat3 = new Mat(resultHeight, resultWidth, binaryMat.type());
            Imgproc.matchTemplate(srcMat3, targetBinaryMat, resultMat3, matchMethod);
            Core.MinMaxLocResult minMaxLocResult3 = Core.minMaxLoc(resultMat3);
            Point matchLocation3 = getMatchLocation(matchMethod, minMaxLocResult3);
            points[2] = new Point(matchLocation3.x + targetBinaryMat.cols() / 2 + binaryMat.cols() / 2, matchLocation3.y + targetBinaryMat.rows() / 2 + binaryMat.rows() / 2);

            mLogList.add("step 3 point 3 : x = " + points[2].x + ", y = " + points[2].y);

            String stepName4 = "左下定位标记分析";
            stepDealProgress(6, total, stepName4, true);
            Mat srcMat4 = new Mat(binaryMat, new Range(binaryMat.rows() / 2, binaryMat.rows()), new Range(0, binaryMat.cols() / 2));
            Mat resultMat4 = new Mat(resultHeight, resultWidth, binaryMat.type());
            Imgproc.matchTemplate(srcMat4, targetBinaryMat, resultMat4, matchMethod);
            Core.MinMaxLocResult minMaxLocResult4 = Core.minMaxLoc(resultMat4);
            Point matchLocation4 = getMatchLocation(matchMethod, minMaxLocResult4);
            points[3] = new Point(matchLocation4.x + targetBinaryMat.cols() / 2, matchLocation4.y + targetBinaryMat.rows() / 2 + binaryMat.rows() / 2);

            mLogList.add("step 3 point 4 : x = " + points[3].x + ", y = " + points[3].y);

            mLogList.add("step 3 completed");

            Point[] templateMathPoints = points.clone();

            if (checkPointsSuccess(refreshPoints(points))) {

                mLogList.add("auto analyze success , finish without circle check");
                for (int i = 0; i < points.length; i++) {
                    Point point = points[i];
                    mLogList.add("point" + (i + 1) + " : x = " + point.x + ", y = " + point.y);
                }

                //定位标记校验成功
                stepDealProgress(total, total, "定位标记校验", true);
                ThreadUtils.runOnUIThread(() -> mActivity.onAnalyzeSrcBitmapComplete(srcBitmap, points, true));
                printAnalyzeProcess(startTime);
                return;
            }
            //(3)=====================================================//


            //(4)=====================================================//
            mLogList.add("auto analyze fail , start circle check");
            //定位标记校验不成功，启用霍夫圆检测
            Point[] pointsAfterCircleCheck = doCircleCheckInPartArea(srcMat1, srcMat2, srcMat3, srcMat4, points);

            points[0] = pointsAfterCircleCheck[0];
            points[1] = pointsAfterCircleCheck[1];
            points[2] = pointsAfterCircleCheck[2];
            points[3] = pointsAfterCircleCheck[3];

            Point[] circleCheckPoints = points.clone();

            if (checkPointsSuccess(points)) {
                stepDealProgress(total, total, "定位标记校验", true);
                ThreadUtils.runOnUIThread(() -> mActivity.onAnalyzeSrcBitmapComplete(srcBitmap, points, true));
                mLogList.add("auto analyze success");
                for (int i = 0; i < points.length; i++) {
                    Point point = points[i];
                    mLogList.add("point" + (i + 1) + " : x = " + point.x + ", y = " + point.y);
                }
                printAnalyzeProcess(startTime);
                return;
            }
            stepDealProgress(total, total, "定位标记校验失败：请重新拍照或手动框选答题区域", false);

            mLogList.add("auto analyze fail");

            ThreadUtils.runOnUIThread(() -> mActivity.onAnalyzeSrcBitmapComplete(srcBitmap, findBetterPoints(templateMathPoints, circleCheckPoints), false));

            printAnalyzeProcess(startTime);

        });
    }

    private void printAnalyzeProcess(long startTime) {
        long finishTime = new Date().getTime();
        mLogList.add("auto analyze finish at : " + TimeUtils.getTimeStr(finishTime));
        mLogList.add("total used time : " + (finishTime - startTime) / 1000.0);

        LogUtils.saveJsonToFile(mLogList, "AutoAnalyze");

        mLogList.clear();
    }

    private Point[] findBetterPoints(Point[] firstPoints, Point[] secondPoints) {
        mLogList.add("find better points calc first angle start");
        double[] firstAngles = calcAngle(firstPoints);
        mLogList.add("find better points calc first angle complete");

        mLogList.add("find better points calc second angle start");
        double[] secondAngles = calcAngle(secondPoints);
        mLogList.add("find better points calc second angle complete");

        double firstDiff = 0;
        for (double firstAngle : firstAngles) {
            firstDiff += Math.abs(firstAngle - 90);
        }
        double secondDiff = 0;
        for (double secondAngle : secondAngles) {
            secondDiff += Math.abs(secondAngle - 90);
        }
        if (firstDiff < secondDiff) {
            mLogList.add("find better points first return");
            return firstPoints;
        } else {
            mLogList.add("find better points second return");
            return secondPoints;
        }
    }

    /**
     * 所有点是否都有效（不含疑似错误的点）
     */
    private boolean checkPointsSuccess(Point[] points) {

        mLogList.add("check points success start");

        for (int i = 0; i < points.length; i++) {
            Point point = points[i];
            mLogList.add("check points success : point " + i + " , x = " + point.x + " , y = " + point.y);
        }

        if (points.length != 4) {
            mLogList.add("check points success complete : false");
            return false;
        }
        int fakePointIndex = findFakePointIndex(points);

        mLogList.add("check points success complete : " + (fakePointIndex == -1));

        return fakePointIndex == -1;
    }

    /**
     * 查找疑似错误的点
     */
    private int findFakePointIndex(Point[] points) {
        for (int i = 0; i < points.length; i++) {
            Point point = points[i];
            if (point.x <= 0 || point.y <= 0) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 计算4个点构成图形的内角
     */
    private double[] calcAngle(Point[] points) {

        final ArrayList<Point> pointsList = new ArrayList<>(Arrays.asList(points));

        Collections.sort(pointsList, (o1, o2) -> {
            float diff = o1.x - o2.x;
            if (diff > 0) {
                return 1;
            } else if (diff < 0) {
                return -1;
            } else {
                return 0;
            }
        });

        double[] angles = new double[4];
        Point tl;
        Point tr;
        Point bl;
        Point br;
        Point l1 = pointsList.get(0);
        Point l2 = pointsList.get(1);
        if (l1.y > l2.y) {
            tl = l2;
            bl = l1;
        } else {
            tl = l1;
            bl = l2;
        }
        Point r1 = pointsList.get(2);
        Point r2 = pointsList.get(3);
        if (r1.y > r2.y) {
            tr = r2;
            br = r1;
        } else {
            tr = r1;
            br = r2;
        }

        float kw1Divide = tr.x - tl.x;
        if (kw1Divide == 0) {
            kw1Divide = 0.0001F;
        }
        final float kw1 = (tr.y - tl.y) * 1F / kw1Divide;

        float kw2Divide = br.x - bl.x;
        if (kw2Divide == 0) {
            kw2Divide = 0.0001F;
        }
        final float kw2 = (br.y - bl.y) * 1F / kw2Divide;

        float kw3Divide = bl.x - tl.x;
        if (kw3Divide == 0) {
            kw3Divide = 0.0001F;
        }
        final float kh1 = (bl.y - tl.y) * 1F / kw3Divide;

        float kw4Divide = br.x - tr.x;
        if (kw4Divide == 0) {
            kw4Divide = 0.0001F;
        }
        final float kh2 = (br.y - tr.y) * 1F / kw4Divide;

        final double kw1Angle = Math.abs(Math.toDegrees(Math.atan(kw1)));
        final double kw2Angle = Math.abs(Math.toDegrees(Math.atan(kw2)));
        final double kh1Angle = Math.abs(Math.toDegrees(Math.atan(kh1)));
        final double kh2Angle = Math.abs(Math.toDegrees(Math.atan(kh2)));

        mLogList.add("calc kw1 angle : " + kw1Angle);
        mLogList.add("calc kw2 angle : " + kw2Angle);
        mLogList.add("calc kh1 angle : " + kh1Angle);
        mLogList.add("calc kh2 angle : " + kh2Angle);

        angles[0] = kh1Angle - kw1Angle;
        angles[1] = 180 + kw1Angle - kh2Angle;
        angles[2] = kh2Angle - kw2Angle;
        angles[3] = 180 + kw2Angle - kh1Angle;

        mLogList.add("calc tl angle : " + angles[0]);
        mLogList.add("calc tr angle : " + angles[1]);
        mLogList.add("calc br angle : " + angles[2]);
        mLogList.add("calc bl angle : " + angles[3]);

        return angles;
    }

    /**
     * 如果4个点构成的图像近似矩形，则认为识别有效
     * 疑似错误的Point用（0，0）替换
     */
    private Point[] refreshPoints(Point[] points) {

        final float acceptRate = 0.15F;
        final float minAcceptRate = 1 - acceptRate;
        final float maxAcceptRate = 1 + acceptRate;

        boolean tlAccept = true;
        boolean trAccept = true;
        boolean blAccept = true;
        boolean brAccept = true;

        double[] angles = calcAngle(points);

        final double tlAngle = angles[0];
        final double trAngle = angles[1];
        final double brAngle = angles[2];
        final double blAngle = angles[3];

        if (tlAngle / 90F < minAcceptRate || tlAngle / 90F > maxAcceptRate) {
            tlAccept = false;
        }

        if (trAngle / 90F < minAcceptRate || trAngle / 90F > maxAcceptRate) {
            trAccept = false;
        }

        if (brAngle / 90F < minAcceptRate || brAngle / 90F > maxAcceptRate) {
            brAccept = false;
        }

        if (blAngle / 90F < minAcceptRate || blAngle / 90F > maxAcceptRate) {
            blAccept = false;
        }

        double w1 = calcPointsDistance(points[1], points[0]);
        double w2 = calcPointsDistance(points[2], points[3]);
        double h1 = calcPointsDistance(points[3], points[0]);
        double h2 = calcPointsDistance(points[2], points[1]);

        mLogList.add("calc w1 length : " + w1);
        mLogList.add("calc w2 length : " + w2);
        mLogList.add("calc h1 length : " + h1);
        mLogList.add("calc h2 length : " + h2);

        boolean finishChangeAngleCheck = false;

        if (tlAccept && !finishChangeAngleCheck) {
            if (w1 / h1 < AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * maxAcceptRate
                    && w1 / h1 > AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * minAcceptRate) {
                trAccept = true;
                blAccept = true;
                finishChangeAngleCheck = true;
            }
        }

        if (trAccept && !finishChangeAngleCheck) {
            if (w1 / h2 < AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * maxAcceptRate
                    && w1 / h2 > AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * minAcceptRate) {
                tlAccept = true;
                brAccept = true;
                finishChangeAngleCheck = true;
            }
        }

        if (brAccept && !finishChangeAngleCheck) {
            if (w2 / h2 < AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * maxAcceptRate
                    && w2 / h2 > AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * minAcceptRate) {
                trAccept = true;
                blAccept = true;
                finishChangeAngleCheck = true;
            }
        }

        if (blAccept && !finishChangeAngleCheck) {
            if (w2 / h1 < AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * maxAcceptRate
                    && w2 / h1 > AnswerSheetConfig.TARGET_AREA_WIDTH_AND_HEIGHT_RATE * minAcceptRate) {
                tlAccept = true;
                brAccept = true;
                finishChangeAngleCheck = true;
            }
        }

        mLogList.add("finish change angle check : " + finishChangeAngleCheck);

        if (!tlAccept) {
            points[0] = new Point(0, 0);
        }
        if (!trAccept) {
            points[1] = new Point(0, 0);
        }
        if (!brAccept) {
            points[2] = new Point(0, 0);
        }
        if (!blAccept) {
            points[3] = new Point(0, 0);
        }
        return points;
    }

    private double calcPointsDistance(Point start, Point end) {
        return Math.sqrt(Math.pow((end.y - start.y), 2) + Math.pow((end.x - start.x), 2));
    }

    private Point[] doCircleCheckInPartArea(Mat tlSrcMat, Mat trSrcMat, Mat blSrcMat, Mat brSrcMat, Point[] points) {

        ArrayList<Point> tlNewPoints = new ArrayList<>();
        tlNewPoints.add(points[0]);

        ArrayList<Point> trNewPoints = new ArrayList<>();
        trNewPoints.add(points[1]);

        ArrayList<Point> brNewPoints = new ArrayList<>();
        brNewPoints.add(points[2]);

        ArrayList<Point> blNewPoints = new ArrayList<>();
        blNewPoints.add(points[3]);

        for (int i = 0; i < points.length; i++) {
            Point point = points[i];
            if (point.x <= 0 || point.y <= 0) {
                switch (i) {
                    case 0: {//tl
                        tlNewPoints.addAll(findCircles(tlSrcMat, "tl circle check"));
                        break;
                    }
                    case 1: {//tr
                        trNewPoints.addAll(findCircles(trSrcMat, "tr circle check"));
                        break;
                    }
                    case 2: {//br
                        brNewPoints.addAll(findCircles(brSrcMat, "br circle check"));
                        break;
                    }
                    case 3: {//bl
                        blNewPoints.addAll(findCircles(blSrcMat, "bl circle check"));
                        break;
                    }
                }
            }
        }

        ArrayList<Point[]> newPointsSet = new ArrayList<>();

        /*tlNewPoints.clear();
        tlNewPoints.add(createPoint(1, 1));
        tlNewPoints.add(createPoint(2, 2));
        tlNewPoints.add(createPoint(3, 3));
        tlNewPoints.add(createPoint(4, 4));

        trNewPoints.clear();
        trNewPoints.add(createPoint(1, 1));
        trNewPoints.add(createPoint(2, 2));
        trNewPoints.add(createPoint(3, 3));

        brNewPoints.clear();
        brNewPoints.add(createPoint(1, 1));
        brNewPoints.add(createPoint(2, 2));
        brNewPoints.add(createPoint(3, 3));
        brNewPoints.add(createPoint(4, 4));
        brNewPoints.add(createPoint(5, 5));

        blNewPoints.clear();
        blNewPoints.add(createPoint(1, 1));
        blNewPoints.add(createPoint(2, 2));*/

        for (int tlIndex = 0; tlIndex < tlNewPoints.size(); tlIndex++) {
            Point tlNewPoint = tlNewPoints.get(tlIndex);
            for (int trIndex = 0; trIndex < trNewPoints.size(); trIndex++) {
                Point trNewPoint = trNewPoints.get(trIndex);
                for (int brIndex = 0; brIndex < brNewPoints.size(); brIndex++) {
                    Point brNewPoint = brNewPoints.get(brIndex);
                    for (int blIndex = 0; blIndex < blNewPoints.size(); blIndex++) {
                        Point blNewPoint = blNewPoints.get(blIndex);
                        Point[] newPoints = new Point[4];
                        newPoints[0] = tlNewPoint;
                        newPoints[1] = trNewPoint;
                        newPoints[2] = brNewPoint;
                        newPoints[3] = blNewPoint;
                        newPointsSet.add(newPoints);
                    }
                }
            }
        }

        ArrayList<Point[]> acceptPointsSet = new ArrayList<>();

        for (Point[] newPoints : newPointsSet) {
            if (checkPointsSuccess(newPoints)) {
                for (int i = 0; i < newPoints.length; i++) {
                    Point newPoint = newPoints[i];
                    mLogList.add("circle check new points : point " + i + " x = " + newPoint.x + " , y = " + newPoint.y);
                }
                if (checkPointsSuccess(refreshPoints(newPoints))) {
                    acceptPointsSet.add(newPoints);
                }
            }
        }

        for (Point[] acceptPoints : acceptPointsSet) {
            for (int i = 0; i < acceptPoints.length; i++) {
                Point newPoint = acceptPoints[i];
                mLogList.add("circle check accept points : point " + i + " x = " + newPoint.x + " , y = " + newPoint.y);
            }
        }

        if (acceptPointsSet.isEmpty()) {
            for (int i = 0; i < points.length; i++) {
                Point point = points[i];
                mLogList.add("circle check fail , return original points : point " + i + " x = " + point.x + " , y = " + point.y);
            }
            return points;
        } else {
            Point[] bestPoints = acceptPointsSet.get(0);
            for (int i = 0; i < acceptPointsSet.size(); i++) {
                Point[] acceptPoints = acceptPointsSet.get(i);
                bestPoints = findBetterPoints(bestPoints, acceptPoints);
            }
            for (int i = 0; i < bestPoints.length; i++) {
                Point bestPoint = bestPoints[i];
                mLogList.add("circle check success , return original points : point " + i + " x = " + bestPoint.x + " , y = " + bestPoint.y);
            }
            return bestPoints;
        }

    }

    /*private Point createPoint(int x, int y) {
        return new Point(x, y);
    }*/

    private List<Point> findCircles(Mat srcMat, String tag) {
        ArrayList<Point> findPoints = new ArrayList<>();
        ArrayList<CircleModel> findCircles = doCircleCheck(srcMat, tag);
        for (CircleModel findCircle : findCircles) {
            Point findPoint = new Point((int) findCircle.x, (int) findCircle.y);
            findPoints.add(findPoint);
        }
        return findPoints;
    }

    private ArrayList<CircleModel> doCircleCheck(Mat srcMat, String tag) {
        ArrayList<CircleModel> findCircles = new ArrayList<>();
        Mat circleCheckMat = new Mat();
        int srcWidth = srcMat.cols();
        int minDist = (int) (srcWidth * 2 * 0.7F);
        int minRadius = (int) ((srcWidth * 2 * AnswerSheetConfig.CIRCLE_RADIUS_RATE) * 0.5F / 2);
        int maxRadius = (int) ((srcWidth * 2 * AnswerSheetConfig.CIRCLE_RADIUS_RATE) * 2.0F / 2);
        mLogList.add("circle check [ " + tag + " ] : src width = " + srcWidth + ", min dist = " + minDist + ", min radius = " + minRadius + ", max radius = " + maxRadius);
        try {
            Imgproc.HoughCircles(srcMat, circleCheckMat, Imgproc.HOUGH_GRADIENT, 1, minDist, 100, 10, minRadius, maxRadius);
            int circleCount = circleCheckMat.cols();
            for (int i = 0; i < circleCount; i++) {
                float[] circleInfo = new float[3];
                circleCheckMat.get(0, i, circleInfo);
                findCircles.add(new CircleModel(circleInfo[0], circleInfo[1], circleInfo[2]));
            }
            if (circleCount == 0) {
                mLogList.add("circle check [ " + tag + " ] : find no circle");
            }
            for (int i = 0; i < findCircles.size(); i++) {
                CircleModel tempCircle = findCircles.get(i);
                mLogList.add("circle check [ " + tag + " ] : circle " + i + " --> x = " + tempCircle.x + ", y = " + tempCircle.y + ", r = " + tempCircle.r);
            }
            return findCircles;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            circleCheckMat.release();
        }
        return findCircles;
    }


    @NonNull
    private Mat doBinary(Mat preMat, int blockSize, int c, final int current, final int total) {
        Mat binaryMat = new Mat();
        try {
            Imgproc.adaptiveThreshold(preMat, binaryMat, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY_INV, blockSize, c);
        } catch (Exception e) {
            binaryMat.release();
            stepDealProgress(current, total, "预处理失败：" + e.getMessage(), false);
        }
        return binaryMat;
    }

    @NonNull
    private Mat doBlur(Mat preMat, final int kSize, final int current, final int total) {
        Mat blurMat = new Mat();
        try {
            Imgproc.GaussianBlur(preMat, blurMat, new Size(kSize, kSize), 0);
        } catch (Exception e) {
            blurMat.release();
            stepDealProgress(current, total, "预处理失败：" + e.getMessage(), false);
        }
        return blurMat;
    }

    @NonNull
    private Mat doGray(Mat preMat, final int current, final int total) {
        Mat grayMat = new Mat();
        try {
            Imgproc.cvtColor(preMat, grayMat, Imgproc.COLOR_BGRA2GRAY);
        } catch (Exception e) {
            grayMat.release();
            stepDealProgress(current, total, "预处理失败：" + e.getMessage(), false);
        }
        return grayMat;
    }

    private void stepDealProgress(final int current, final int total, final String stepName, final boolean success) {
        ThreadUtils.runOnUIThread(() -> mActivity.onAnalyzeSrcBitmapProgress(stepName, current, total, success));
    }

    private Bitmap createBitmapAsSrc(Bitmap srcBitmap) {
        return Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
    }

    private Point getMatchLocation(int matchMethod, Core.MinMaxLocResult minMaxLocResult) {
        Point matchLocation;
        if (matchMethod == Imgproc.TM_SQDIFF || matchMethod == Imgproc.TM_SQDIFF_NORMED) {
            matchLocation = new Point(((int) minMaxLocResult.minLoc.x), ((int) minMaxLocResult.minLoc.y));
        } else {
            matchLocation = new Point(((int) minMaxLocResult.maxLoc.x), ((int) minMaxLocResult.maxLoc.y));
        }
        return matchLocation;
    }

    private String createCropFilePath() {
        String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA).format(new java.util.Date());
        String imageFileName = "CROP" + timeStamp;
        return FilePath.IMAGE_DIR + File.separator + imageFileName + ".png";
    }
}
